home *** CD-ROM | disk | FTP | other *** search
/ The Arsenal Files 8 / The Arsenal Files Collection #8 (Arsenal Computer) (1996).ISO / prg_gen / euphor14.zip / GURU.EX < prev    next >
Text File  |  1996-09-05  |  16KB  |  664 lines

  1. -- Guru
  2. -- usage:
  3. -- to search EUPHORIA directories:
  4. --        guru word1 word2 word3 ...
  5. --
  6. -- to search the current directory:
  7. --      cdguru word1 word2 word3 ...
  8. --
  9. -- Searches for the best articles that contain the words that you type.
  10. -- Each word can contain * and ? wildcard characters.
  11. -- The articles are given a score and presented to you sorted by score.
  12. -- The scoring system strongly favors articles that contain several of 
  13. -- your words, rather than just several occurrences of one of your words.
  14. -- Some very common words are ignored (see noise_words).
  15. -- e.g.
  16. --        guru sequence* atom *pend g?r?
  17. --
  18. -- Results are displayed on screen and also saved in "c:\guru.out"
  19. -- Hints - remember to add * to words that can be pluralized or have 
  20. --         many different endings.
  21. --       - enter an important word twice to double the value of that word  
  22.  
  23. without type_check
  24.  
  25. include file.e
  26. include wildcard.e
  27. include graphics.e
  28. include sort.e
  29.  
  30. -------- some user-modifiable parameters: 
  31.  
  32. sequence log_name, log_path
  33. log_name = "guru.out"
  34. log_path = "c:\\" & log_name  -- place to store results
  35.  
  36. -- files to skip:
  37. sequence skip_list 
  38. skip_list = {
  39.     "*.EXE", "*.DLL", "*.LIB", "*.OBJ", 
  40.     "*.SWP", "*.PAR", "*.ZIP", "*.BMP", "*.GIF", "*.JPG"
  41. }
  42.  
  43. -- ignore these extremely common words when searching
  44. sequence noise_words
  45. noise_words = {
  46.     "a", "an", "the", "to", "and", "of", "is", "or", "by", "as", "in",
  47.     "you", "are", "be", "if", "?", "*"
  48. }
  49. constant separator_line = repeat(196, 5)
  50.  
  51. constant MAX_CHUNKS = 20        -- maximum number of chunks to display
  52.  
  53. -- desired size for a chunk of text:
  54. constant MIN_CHUNK_SIZE = 10,   -- minimum number of lines
  55.      MAX_CHUNK_SIZE = 20    -- maximum number of lines
  56.  
  57. constant  LEFT_HIGHLIGHT = 17,  -- highlight markers for matched words
  58.      RIGHT_HIGHLIGHT = 16   -- (assume LEFT_HIGHLIGHT > RIGHT_HIGHLIGHT)
  59.  
  60. constant HIGHLIGHT_COLOR = BRIGHT_WHITE
  61.      
  62. -------- end of user-modifiable parameters 
  63.  
  64. constant KEYB = 0, SCREEN = 1, ERR = 2
  65.  
  66. constant TRUE = 1, FALSE = 0
  67. constant EOF = -1
  68.  
  69. type boolean(integer x)
  70.     return x = 0 or x = 1
  71. end type
  72.  
  73. sequence pos, word_list, word_count, string, orig_string, file_spec
  74.  
  75. boolean wild_string, euphoria
  76.  
  77. integer count_line
  78.  
  79. atom start_time
  80. integer log_file
  81.  
  82. constant LINE_WIDTH = 83
  83.  
  84. function clean(sequence line)
  85. -- replace any funny control characters 
  86. -- and put in \n's to help break up long lines
  87.     sequence new_line
  88.     integer c, col
  89.     
  90.     new_line = ""
  91.     col = 1
  92.     for i = 1 to length(line) do
  93.     if col > LINE_WIDTH then
  94.         new_line = append(new_line, '\n')
  95.         col = 1
  96.     end if
  97.     c = line[i]
  98.     col = col + 1
  99.     if c < 14 then
  100.         if c = '\n' then
  101.         col = 1
  102.         elsif c = '\r' then
  103.         c = ' '    
  104.         elsif c !=  '\t' then
  105.         c = '.'
  106.         end if
  107.     end if
  108.     new_line = append(new_line, c)
  109.     end for
  110.     return new_line
  111. end function
  112.  
  113. boolean display
  114. display = TRUE
  115.  
  116. procedure both_puts(object text)
  117.     puts(log_file, text)
  118.     if display then
  119.     puts(SCREEN, text)
  120.     end if
  121. end procedure
  122.  
  123. procedure both_printf(sequence format, object values)
  124.     printf(log_file, format, values)
  125.     if display then
  126.     printf(SCREEN, format, values)
  127.     end if
  128. end procedure
  129.  
  130. constant MAX_LINE = 100 
  131.  
  132. -- space for largest line
  133. sequence buff
  134. buff = repeat(0, MAX_LINE)
  135.  
  136. function safe_gets(integer fn)
  137. -- Return the next line of text - always with \n on the end.
  138. -- Lines are split at MAX_LINE to prevent
  139. -- "out of memory" problems on humongous lines
  140. -- and to reduce the amount of extraneous output.
  141.     integer c
  142.     
  143.     for b = 1 to MAX_LINE-1 do
  144.     c = getc(fn)
  145.     if c <= LEFT_HIGHLIGHT then
  146.         if c = '\n' then
  147.         buff[b] = c
  148.         return buff[1..b]
  149.         elsif c = EOF then
  150.         if b = 1 then
  151.             return EOF
  152.         else
  153.             buff[b] = '\n'
  154.             return buff[1..b]
  155.         end if
  156.         elsif c >= RIGHT_HIGHLIGHT or c = 0 then
  157.         c = '.'
  158.         end if
  159.     end if
  160.     buff[b] = c
  161.     end for
  162.     buff[MAX_LINE] = '\n'
  163.     return buff[1..MAX_LINE]
  164. end function
  165.  
  166. function sum(sequence s)
  167. -- sum of a sequence
  168.     atom sum
  169.     
  170.     sum = 0
  171.     for i = 1 to length(s) do
  172.     sum = sum + s[i]
  173.     end for
  174.     return sum
  175. end function
  176.  
  177. object line
  178. integer line_next
  179. boolean words_on_line
  180.  
  181. sequence char_class
  182. --    0 means not legitimate
  183. --    1 means legitimate char
  184. --  > 1 means possible first char of matching word
  185. char_class = repeat(0, 255)
  186. char_class['A'..'Z'] = 1
  187. char_class['a'..'z'] = 1
  188. char_class['0'..'9'] = 1
  189. char_class['_'] = 1
  190.  
  191. function has_punctuation(sequence word)
  192. -- TRUE if word contains any punctuation characters 
  193.     integer c
  194.     for i = 1 to length(word) do
  195.     c = word[i]
  196.     if char_class[c] = 0 and c != '?' and c != '*' then
  197.         return TRUE
  198.     end if
  199.     end for
  200.     return FALSE
  201. end function
  202.  
  203. function next_word()
  204. -- Return next possible matching word from line
  205. -- based on first letter of the word.
  206.     sequence word
  207.     integer c
  208.  
  209.     while TRUE do
  210.     -- skip white space:
  211.     while TRUE do
  212.         c = line[line_next]
  213.         line_next = line_next + 1
  214.         if char_class[c] > 0 then
  215.         exit
  216.         elsif c = '\n' then -- there's always a '\n' at end of line 
  217.         return -1
  218.         end if
  219.     end while
  220.     
  221.     words_on_line = TRUE
  222.     
  223.     -- check first letter in word:
  224.     if char_class[c] > 1 then
  225.         -- possible matching word
  226.         word = {c}
  227.         -- read rest of word
  228.         while TRUE do
  229.         c = line[line_next]
  230.         if char_class[c] = 0 then
  231.             return word
  232.         end if
  233.         line_next = line_next + 1
  234.         word = word & c
  235.         end while
  236.     else
  237.         -- not a possible matching word -skip it
  238.         while TRUE do
  239.         c = line[line_next]
  240.         if char_class[c] = 0 then
  241.             exit
  242.         end if
  243.         line_next = line_next + 1
  244.         end while
  245.     end if
  246.     end while
  247. end function
  248.  
  249. sequence chunk_list
  250. chunk_list = {{-1, {}, {}}}
  251.  
  252. integer worst_chunk, worst_score
  253.  
  254. procedure highlight(sequence text)
  255. -- print a line with highlighted words in color
  256.     integer c
  257.     
  258.     if not display then
  259.     return
  260.     end if
  261.     for i = 1 to length(text) do
  262.     c = text[i]
  263.     if c = LEFT_HIGHLIGHT then
  264.         text_color(HIGHLIGHT_COLOR)
  265.     elsif c = RIGHT_HIGHLIGHT then
  266.         text_color(WHITE)
  267.     else
  268.         puts(SCREEN, c)
  269.     end if
  270.     end for
  271. end procedure
  272.  
  273. procedure print_chunk_list()
  274. -- print the best chunks found
  275.     sequence chunk, line
  276.  
  277.     position(count_line, 1)
  278.     for i = 1 to length(word_list) do
  279.     both_printf("%s:%d ", {word_list[i], word_count[i]})
  280.     end for
  281.     position(count_line+1, 1)
  282.     puts(SCREEN, repeat(' ', 80))
  283.     puts(log_file, '\n')
  284.     
  285.     for i = 1 to length(chunk_list) - 1 do
  286.     if i > 1 and display then
  287.         text_color(GREEN)
  288.         puts(SCREEN, "\nPress q to quit, Enter for more:")
  289.         text_color(WHITE)
  290.         puts(SCREEN, " ")
  291.         if getc(0) = 'q' then
  292.         display = FALSE
  293.         end if
  294.     end if
  295.     text_color(RED)
  296.     both_printf("\n#%d of %d ------ %s --- score: %d ------\n", 
  297.             {i, length(chunk_list)-1, 
  298.             chunk_list[i][2], 100 * chunk_list[i][1] + 0.5})
  299.     text_color(WHITE)
  300.     chunk = chunk_list[i][3]
  301.     wrap(FALSE)
  302.     for j = 1 to length(chunk) do
  303.         line = clean(chunk[j])
  304.         highlight(line)
  305.         puts(log_file, line)        
  306.     end for
  307.     wrap(TRUE)
  308.     end for
  309.     if length(chunk_list) > 1 then
  310.     text_color(GREEN)
  311.     puts(SCREEN, "\nSee " & log_path & '\n')
  312.     end if
  313.     text_color(WHITE)
  314.     puts(SCREEN, " \n")
  315. end procedure
  316.  
  317. procedure save_chunk(sequence file_name, sequence chunk, atom score)
  318. -- record an interesting chunk on the chunk list 
  319.  
  320.     score = score / (10 + sqrt(length(chunk))) -- reduce slightly for larger chunks
  321.     for i = 1 to length(chunk_list) do
  322.     if score > chunk_list[i][1] then
  323.         -- insert chunk into list at proper position
  324.         chunk_list = append(chunk_list[1..i-1], {score, file_name, chunk}) 
  325.              & chunk_list[i..length(chunk_list)]
  326.         if length(chunk_list) >  MAX_CHUNKS+1 then
  327.         -- drop the worst chunk on the list
  328.         chunk_list = chunk_list[1..length(chunk_list)-1]
  329.         end if
  330.         exit
  331.     end if
  332.     end for
  333. end procedure
  334.  
  335. sequence wild_word
  336.  
  337. procedure scan(sequence file_name)
  338. -- read next file 
  339.     integer fileNum, first_hit, last_hit, new_chunk
  340.     sequence lword, chunk, word_value
  341.     object word
  342.     atom chunk_total, line_total
  343.     boolean doc_file, matched, first_match
  344.     
  345.     fileNum = open(file_name, "rb")   
  346.     if fileNum = -1 then
  347.     return 
  348.     end if
  349.     
  350.     -- is it a Euphoria .doc file?
  351.     doc_file = euphoria and match(".DOC", file_name) 
  352.     
  353.     -- update display
  354.     wrap(FALSE)
  355.     position(count_line, 1)
  356.     for i = 1 to length(word_list) do
  357.     printf(SCREEN, "%s:%d ", {word_list[i], word_count[i]})
  358.     end for
  359.     position(count_line+1, 1)
  360.     puts(SCREEN, "searching: " & file_name & repeat(' ', 80) & '\r')
  361.     wrap(TRUE)
  362.     
  363.     new_chunk = TRUE
  364.     while TRUE do
  365.     -- initialize
  366.     if new_chunk then
  367.         chunk = {}
  368.         chunk_total = 0
  369.         first_hit = 0
  370.         last_hit = 0
  371.         new_chunk = FALSE
  372.         word_value = repeat(1, length(word_list))
  373.     end if
  374.     line_next = 1
  375.     line_total = 0
  376.  
  377.     -- read next line
  378.     line = safe_gets(fileNum)
  379.     if atom(line) then
  380.         exit -- end of file
  381.     end if
  382.     
  383.     words_on_line = FALSE
  384.     
  385.     while TRUE do
  386.         -- read next word in line
  387.         word = next_word()
  388.         if atom(word) then
  389.         exit
  390.         end if
  391.         lword = lower(word)
  392.         first_match = TRUE
  393.         for i = 1 to length(word_list) do
  394.         if wild_word[i] then
  395.             -- slow
  396.             matched = wildcard_match(word_list[i], lword) 
  397.         else
  398.             -- fast
  399.             matched = compare(word_list[i], lword) = 0
  400.         end if
  401.         if matched then
  402.             -- score a bit higher for matching a non-wildcard word
  403.             line_total = line_total + 
  404.                  word_value[i] * (1.0 
  405.                     + 0.5 * (match(separator_line, line) != 0) 
  406.                     + 0.3 * (not wild_word[i])
  407.                     + 0.3 * doc_file)
  408.             word_count[i] = word_count[i] + 1
  409.             word_value[i] = word_value[i] / 2
  410.             if first_match then
  411.             first_match = FALSE
  412.             line = line[1..line_next - length(word) - 1] & 
  413.                     LEFT_HIGHLIGHT &
  414.                     word & 
  415.                     RIGHT_HIGHLIGHT & 
  416.                     line[line_next..length(line)]
  417.             line_next = line_next + 2
  418.             end if
  419.         end if
  420.         end for
  421.     end while
  422.     chunk = append(chunk, line)
  423.     
  424.     -- decide chunk boundaries
  425.     if words_on_line then
  426.         if line_total > 0 then
  427.         chunk_total = chunk_total + line_total
  428.         last_hit = length(chunk)
  429.         if first_hit = 0 then
  430.             first_hit = last_hit
  431.         end if
  432.         end if
  433.         if chunk_total > 0 then
  434.         if (line_total = 0 and 
  435.             last_hit < length(chunk) - MIN_CHUNK_SIZE/2 and
  436.             length(chunk) >= MIN_CHUNK_SIZE) or
  437.             length(chunk) >= MAX_CHUNK_SIZE then
  438.             
  439.             if length(chunk) <= MIN_CHUNK_SIZE then
  440.             first_hit = 1
  441.             last_hit = length(chunk)
  442.             else
  443.             -- trim off some context, but not all
  444.             first_hit = floor((first_hit + 1) / 2)
  445.             last_hit = floor((last_hit + length(chunk)) / 2)
  446.             end if
  447.             
  448.             save_chunk(file_name, 
  449.                    chunk[first_hit..last_hit], 
  450.                    chunk_total)
  451.             new_chunk = TRUE
  452.         end if
  453.         elsif length(chunk) >= MIN_CHUNK_SIZE then
  454.         new_chunk = TRUE
  455.         end if
  456.     elsif chunk_total = 0 and length(chunk) > MIN_CHUNK_SIZE/2 then
  457.         new_chunk = TRUE
  458.     end if
  459.     end while
  460.     if chunk_total > 0 then
  461.     save_chunk(file_name, chunk, chunk_total)
  462.     end if
  463.     close(fileNum)
  464.     return 
  465. end procedure
  466.  
  467. procedure look_at(sequence path_name, sequence entry)
  468. -- see if a file name qualifies for searching
  469.     sequence file_name
  470.     
  471.     file_name = entry[D_NAME]
  472.     if compare(file_name, log_name) = 0 then
  473.     return -- avoid circularity
  474.     end if
  475.     -- check skip list
  476.     for i = 1 to length(skip_list) do
  477.     if wildcard_file(skip_list[i], file_name) then
  478.         return
  479.     end if
  480.     end for
  481.     path_name = path_name & '\\'
  482.     if compare(path_name[1..2], ".\\") = 0 then
  483.     path_name = path_name[3..length(path_name)]
  484.     end if
  485.     path_name = path_name & file_name
  486.     scan(path_name)
  487. end procedure
  488.  
  489. procedure walk_dir(sequence path_name)
  490. -- walk through a directory and its subdirectories 
  491. -- "looking" at each file
  492.     object d
  493.     integer key
  494.     
  495.     d = dir(path_name)
  496.     while find(path_name[length(path_name)], " \\") do
  497.     path_name = path_name[1..length(path_name)-1]
  498.     end while
  499.     if atom(d) then
  500.     return
  501.     end if
  502.     d = sort(d)
  503.     for i = 1 to length(d) do
  504.     if find('d', d[i][D_ATTRIBUTES]) then
  505.         if not find(d[i][D_NAME], {".", ".."}) then
  506.         walk_dir(path_name & '\\' & d[i][D_NAME])
  507.         end if
  508.     else
  509.         look_at(path_name, d[i])
  510.     end if
  511.     key = get_key()
  512.     if key = 'q' then
  513.         print_chunk_list()
  514.         abort(1)
  515.     end if
  516.     end for
  517. end procedure
  518.  
  519. procedure usage(sequence g)
  520.     text_color(MAGENTA)
  521.     puts(SCREEN, "\n\t\t" & g & " Guru\n\n")
  522.     text_color(WHITE)
  523.     puts(SCREEN, 
  524.     "Enter keywords that will define the subject you are interested in. \n") 
  525.     puts(SCREEN, 
  526.     " - Upper/lower case is not important.\n")
  527.     puts(SCREEN, 
  528.     " - Words may contain * and ? wildcard characters,\n")
  529.     puts(SCREEN, 
  530.     " - example ---> get? input *routine*\n\n")
  531.     puts(SCREEN, "---> ")
  532. end procedure
  533.  
  534. function blank_delim(sequence s)
  535. -- break up a blank-delimited string
  536.     sequence list, segment
  537.     integer i
  538.     list = {}
  539.     i = 1
  540.     while i < length(s) do
  541.     while find(s[i], " \t") do
  542.         i = i + 1
  543.     end while
  544.     if s[i] = '\n' then
  545.         exit
  546.     end if
  547.     segment = ""
  548.     while not find(s[i], " \t\n") do
  549.         segment = segment & s[i]
  550.         i = i + 1
  551.     end while
  552.     list = append(list, segment)
  553.     end while
  554.     return list
  555. end function
  556.  
  557. log_name = upper(log_name)
  558.  
  559. sequence cmd
  560. cmd = command_line()  -- ex guru.ex words...
  561.  
  562. euphoria = FALSE
  563. if length(cmd) < 3 then
  564.     usage("Current Directory")
  565.     cmd = blank_delim(gets(0))
  566.     puts(SCREEN, '\n')
  567. elsif compare(cmd[3], "E!") = 0 then
  568.     -- search Euphoria directories
  569.     euphoria = TRUE
  570.     if length(cmd) <= 3 then
  571.     usage("Euphoria")
  572.     cmd = blank_delim(gets(0))
  573.     puts(SCREEN, '\n')
  574.     else
  575.     cmd = cmd[4..length(cmd)]
  576.     end if
  577. else
  578.     cmd = cmd[3..length(cmd)]
  579. end if
  580.  
  581. log_file = open(log_path, "w")
  582. if log_file = -1 then
  583.     puts(ERR, "Couldn't open " & log_path & '\n')
  584.     abort(1)
  585. end if
  586.  
  587. word_list = {}
  588. wild_word = {}
  589. for i = 1 to length(cmd) do
  590.     cmd[i] = lower(cmd[i])
  591.     if find(cmd[i], noise_words) then
  592.     puts(SCREEN, "ignoring: " & cmd[i] & "   (too common)\n")
  593.     elsif has_punctuation(cmd[i]) then
  594.     puts(SCREEN, "ignoring: " & cmd[i] & 
  595.     "   (contains punctuation character)\n")
  596.     else
  597.     word_list = append(word_list, cmd[i])
  598.     wild_word = append(wild_word, find('*', cmd[i]) or find('?', cmd[i]))
  599.     end if
  600. end for
  601.  
  602. if length(word_list) = 0 then
  603.     abort(1)
  604. end if
  605. word_count = repeat(0, length(word_list))
  606.  
  607.  
  608. integer first_char
  609. -- prepare char_class[] for efficient detection of a 
  610. -- possible first letter in one of the words
  611. for i = 1 to length(word_list) do
  612.     first_char = word_list[i][1]
  613.     if first_char = '*' or first_char = '?' then
  614.     char_class = char_class * 2 -- select all allowed chars
  615.     exit
  616.     elsif char_class[first_char] > 0 then
  617.     char_class[first_char] = 2
  618.     -- select upper or lower case
  619.     if first_char >= 'A' and first_char <= 'Z' then
  620.         char_class[first_char - 'A' + 'a'] = 2
  621.     elsif first_char >= 'a' and first_char <= 'z' then
  622.         char_class[first_char - 'a' + 'A'] = 2
  623.     end if
  624.     end if
  625. end for
  626.  
  627. file_spec = {"*.*"}
  628.  
  629. -- quits after finishing current file
  630. puts(SCREEN, "Press q to quit\n\n\n") 
  631.  
  632. sequence gp
  633. gp = get_position()
  634. count_line = gp[1]-1
  635.  
  636. object d
  637.  
  638. if euphoria then
  639.     d = getenv("EUDIR")
  640.     if atom(d) then
  641.     d = "C:\\EUPHORIA"
  642.     end if
  643.     if sequence(dir(d)) then
  644.     -- reduce noise in Euphoria Help
  645.     skip_list = skip_list & {"*.BAS", "*.BAT", "LW.DOC", "BIND.EX", 
  646.                  "EX.ERR"}
  647.     walk_dir(d)
  648.     print_chunk_list()
  649.     abort(0)
  650.     end if
  651. end if
  652.  
  653. puts(log_file, "Searching " & current_dir() & "\n\n")
  654. if sequence(dir(".")) then
  655.     walk_dir(".")
  656. else
  657.     walk_dir(current_dir())
  658. end if
  659.  
  660. print_chunk_list()
  661.  
  662. without warning
  663.  
  664.